package spnego
import (
"context"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/jcmturner/gofork/encoding/asn1"
"github.com/jcmturner/gokrb5/v8/asn1tools"
"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/credentials"
"github.com/jcmturner/gokrb5/v8/gssapi"
"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
"github.com/jcmturner/gokrb5/v8/iana/msgtype"
"github.com/jcmturner/gokrb5/v8/krberror"
"github.com/jcmturner/gokrb5/v8/messages"
"github.com/jcmturner/gokrb5/v8/service"
"github.com/jcmturner/gokrb5/v8/types"
)
const (
TOK_ID_KRB_AP_REQ = "0100"
TOK_ID_KRB_AP_REP = "0200"
TOK_ID_KRB_ERROR = "0300"
)
type KRB5Token struct {
OID asn1 .ObjectIdentifier
tokID []byte
APReq messages .APReq
APRep messages .APRep
KRBError messages .KRBError
settings *service .Settings
context context .Context
}
func (m *KRB5Token ) Marshal () ([]byte , error ) {
b , _ := asn1 .Marshal (m .OID )
b = append (b , m .tokID ...)
var tb []byte
var err error
switch hex .EncodeToString (m .tokID ) {
case TOK_ID_KRB_AP_REQ :
tb , err = m .APReq .Marshal ()
if err != nil {
return []byte {}, fmt .Errorf ("error marshalling AP_REQ for MechToken: %v" , err )
}
case TOK_ID_KRB_AP_REP :
return []byte {}, errors .New ("marshal of AP_REP GSSAPI MechToken not supported by gokrb5" )
case TOK_ID_KRB_ERROR :
return []byte {}, errors .New ("marshal of KRB_ERROR GSSAPI MechToken not supported by gokrb5" )
}
if err != nil {
return []byte {}, fmt .Errorf ("error mashalling kerberos message within mech token: %v" , err )
}
b = append (b , tb ...)
return asn1tools .AddASNAppTag (b , 0 ), nil
}
func (m *KRB5Token ) Unmarshal (b []byte ) error {
var oid asn1 .ObjectIdentifier
r , err := asn1 .UnmarshalWithParams (b , &oid , fmt .Sprintf ("application,explicit,tag:%v" , 0 ))
if err != nil {
return fmt .Errorf ("error unmarshalling KRB5Token OID: %v" , err )
}
if !oid .Equal (gssapi .OIDKRB5 .OID ()) {
return fmt .Errorf ("error unmarshalling KRB5Token, OID is %s not %s" , oid .String (), gssapi .OIDKRB5 .OID ().String ())
}
m .OID = oid
if len (r ) < 2 {
return fmt .Errorf ("krb5token too short" )
}
m .tokID = r [0 :2 ]
switch hex .EncodeToString (m .tokID ) {
case TOK_ID_KRB_AP_REQ :
var a messages .APReq
err = a .Unmarshal (r [2 :])
if err != nil {
return fmt .Errorf ("error unmarshalling KRB5Token AP_REQ: %v" , err )
}
m .APReq = a
case TOK_ID_KRB_AP_REP :
var a messages .APRep
err = a .Unmarshal (r [2 :])
if err != nil {
return fmt .Errorf ("error unmarshalling KRB5Token AP_REP: %v" , err )
}
m .APRep = a
case TOK_ID_KRB_ERROR :
var a messages .KRBError
err = a .Unmarshal (r [2 :])
if err != nil {
return fmt .Errorf ("error unmarshalling KRB5Token KRBError: %v" , err )
}
m .KRBError = a
}
return nil
}
func (m *KRB5Token ) Verify () (bool , gssapi .Status ) {
switch hex .EncodeToString (m .tokID ) {
case TOK_ID_KRB_AP_REQ :
ok , creds , err := service .VerifyAPREQ (&m .APReq , m .settings )
if err != nil {
return false , gssapi .Status {Code : gssapi .StatusDefectiveToken , Message : err .Error()}
}
if !ok {
return false , gssapi .Status {Code : gssapi .StatusDefectiveCredential , Message : "KRB5_AP_REQ token not valid" }
}
m .context = context .Background ()
m .context = context .WithValue (m .context , ctxCredentials , creds )
return true , gssapi .Status {Code : gssapi .StatusComplete }
case TOK_ID_KRB_AP_REP :
return false , gssapi .Status {Code : gssapi .StatusFailure , Message : "verifying an AP_REP is not currently supported by gokrb5" }
case TOK_ID_KRB_ERROR :
if m .KRBError .MsgType != msgtype .KRB_ERROR {
return false , gssapi .Status {Code : gssapi .StatusDefectiveToken , Message : "KRB5_Error token not valid" }
}
return true , gssapi .Status {Code : gssapi .StatusUnavailable }
}
return false , gssapi .Status {Code : gssapi .StatusDefectiveToken , Message : "unknown TOK_ID in KRB5 token" }
}
func (m *KRB5Token ) IsAPReq () bool {
if hex .EncodeToString (m .tokID ) == TOK_ID_KRB_AP_REQ {
return true
}
return false
}
func (m *KRB5Token ) IsAPRep () bool {
if hex .EncodeToString (m .tokID ) == TOK_ID_KRB_AP_REP {
return true
}
return false
}
func (m *KRB5Token ) IsKRBError () bool {
if hex .EncodeToString (m .tokID ) == TOK_ID_KRB_ERROR {
return true
}
return false
}
func (m *KRB5Token ) Context () context .Context {
return m .context
}
func NewKRB5TokenAPREQ (cl *client .Client , tkt messages .Ticket , sessionKey types .EncryptionKey , GSSAPIFlags []int , APOptions []int ) (KRB5Token , error ) {
var m KRB5Token
m .OID = gssapi .OIDKRB5 .OID ()
tb , _ := hex .DecodeString (TOK_ID_KRB_AP_REQ )
m .tokID = tb
auth , err := krb5TokenAuthenticator (cl .Credentials , GSSAPIFlags )
if err != nil {
return m , err
}
APReq , err := messages .NewAPReq (
tkt ,
sessionKey ,
auth ,
)
if err != nil {
return m , err
}
for _ , o := range APOptions {
types .SetFlag (&APReq .APOptions , o )
}
m .APReq = APReq
return m , nil
}
func krb5TokenAuthenticator(creds *credentials .Credentials , flags []int ) (types .Authenticator , error ) {
auth , err := types .NewAuthenticator (creds .Domain (), creds .CName ())
if err != nil {
return auth , krberror .Errorf (err , krberror .KRBMsgError , "error generating new authenticator" )
}
auth .Cksum = types .Checksum {
CksumType : chksumtype .GSSAPI ,
Checksum : newAuthenticatorChksum (flags ),
}
return auth , nil
}
func newAuthenticatorChksum(flags []int ) []byte {
a := make ([]byte , 24 )
binary .LittleEndian .PutUint32 (a [:4 ], 16 )
for _ , i := range flags {
if i == gssapi .ContextFlagDeleg {
x := make ([]byte , 28 -len (a ))
a = append (a , x ...)
}
f := binary .LittleEndian .Uint32 (a [20 :24 ])
f |= uint32 (i )
binary .LittleEndian .PutUint32 (a [20 :24 ], f )
}
return a
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .